package pong;

import java.awt.Point;
import java.awt.Rectangle;
import javax.swing.JFrame;

enum VerticalDirection { UP, DOWN}


class PongModel implements PongModelInterface
{
    //Used to hold the scores of Player1 and Player2
    private Pair<Integer> scores = new Pair<>(0,0);
    private Ball ball;
    private Paddle paddle1, paddle2;
    //Represents the Playing Court
    private Rectangle border;
    //Used to offset the Paddle from the edges of the Board
    private final int PADDLE_X_OFFSET = 20;
    
    public PongModel() 
    {
        scores = new Pair<>(0,0);
        border = new Rectangle(0, 0, 600, 400);
        ball = new Ball( new Point(border.width/2, border.height/2));
        paddle1 = new Paddle( new Point(PADDLE_X_OFFSET,border.height/2));
        paddle2 = new Paddle(new Point(border.width - (PADDLE_X_OFFSET + paddle1.width)
                        ,border.height/2));
    }

    @Override
    public void movePaddle(VerticalDirection dir, Player player) 
    {
        if(player == Player.ONE)
        {
            if (isPaddleInBounds(dir, paddle1))
                paddle1.move(dir);
        }
        else
        {
            if (isPaddleInBounds(dir, paddle2))
                paddle2.move(dir);
        }
    }

    @Override
    public boolean update() 
    { 
        // update ball
        ball.move();
        // check paddle intersection
        if (paddleCollision(paddle1) || paddleCollision(paddle2)) 
            ball.verticalBounce();
        // check border intersection
        if (borderCollision())
            ball.horizontalBounce();
        // bounce / update scores / ball location if needed
        if ( !isBallInBounds())
        {
            updateScore();
            ball.init();
            return true;
        }
        return false;    
    }

    /**
     * Detects if the Ball has collided with the specified Paddle
     * @param paddle the Paddle to test for a collision
     * @return true if the bounding Rectangle around the Ball has collided
     * with the Paddle;otherwise it returns false
     */
    boolean paddleCollision(Paddle paddle)
    {
        return validateCollisionDirection(paddle) &&
                xCollision(paddle) && yCollision(paddle);
    }
    
    /**
     * Checks to see if the current Direction of the Ball can result in a collision with the 
     * specified Paddle.
     * @param paddle the paddle to check for a collision
     * @return true if the current Ball's Direction can result in a collision 
     * with the Paddle
     */
    boolean validateCollisionDirection(Paddle paddle)
    {
        //paddle1 can only have a collision if the Ball is going in a Left Direction
        //while paddle2 can only have a collision if the Ball is going
        //in a Right Direction
        return paddle == paddle1 ?
            ball.getDirection() == Direction.UP_LEFT ||
            ball.getDirection() == Direction.DOWN_LEFT
         :
            ball.getDirection() == Direction.UP_RIGHT ||
            ball.getDirection() == Direction.DOWN_RIGHT;
    }
    
    /**
     * Checks to see if the Ball has collided with the x Coordinate of the 
     * specified Paddle
     * @param paddle the Paddle to check for collision
     * @return true if the Ball has collided with the x coordinate  of the
     * specified Paddle
     */
    boolean xCollision(Paddle paddle) 
    {
        return  paddle == paddle1 ? 
            //Paddle1 xCollision Test
            (ball.getCurrentLocation().x >= paddle.x && ball.getCurrentLocation().x 
                <= paddle.x + paddle.width) 
        :
            //Paddle2 xCollision Test
            (ball.getCurrentLocation().x + ball.getSize() >= paddle.x && 
             ball.getCurrentLocation().x + ball.getSize() <= paddle.x + paddle.width);
                
    }
    
    /**
     * Checks to see if the Ball has collided with the y Coordinate of the 
     * specified Paddle
     * @param paddle the Paddle to check for collision
     * @return true if the Ball has collided with the y coordinate  of the
     * specified Paddle
     */
    boolean yCollision(Paddle paddle) 
    {
        return ball.getCurrentLocation().y  >= paddle.y && ball.getCurrentLocation().y 
                        <= paddle.y + paddle.height;
    }
    /**
     * Determines if the Ball has collided with the top or bottom
     * border of the Pong Court
     * @return true if the Ball has collided with the top or bottom
     * border of the game;otherwise it returns false
     */
    boolean borderCollision()
    {
         return  ball.getCurrentLocation().y <= border.y ||
                 ball.getCurrentLocation().y + ball.getSize() 
                 >= border.height;
    }
    
    @Override
    public int getScore(Player player) 
    {
        return player == Player.ONE ? scores.getFirst() : scores.getSecond();
    }

    @Override
    public Rectangle getPongCourt() 
    {
        return new Rectangle(border); //defensive copy
    }

    /**
     * Determines if the specified Paddle is within the bounds of the Pong Court
     * @param dir the VerticalDirection to move the Paddle
     * @param paddle the Paddle to test for in bounds
     * @return 
     */
    boolean isPaddleInBounds(VerticalDirection dir, Paddle paddle) 
    {
        return dir == VerticalDirection.UP ? paddle.getLocation().y > border.y :
                paddle.getLocation().y + paddle.getHeight() < border.getHeight();
    }
    
    /**
     * Determines if the Ball is within the bounds of the Pong court
     * @return true if the Ball is within the court bounds;otherwise it returns
     * false
     */
    boolean isBallInBounds() 
    {
        return ball.getCurrentLocation().x >= border.x && ball.getCurrentLocation().x
                + ball.getSize() <= border.getWidth();
    }

    /**
     * Updates the score of the game based off who won the volley
     */
    void updateScore() 
    {
        if (ball.getCurrentLocation().x  > 0)
            scores.setFirst(scores.getFirst()+ 1);
        else  
            scores.setSecond(scores.getSecond() + 1);
    }

    @Override
    public Point getBallLocation() 
    {
        return ball.getCurrentLocation();
    }

    @Override
    public int getBallSize() 
    {
        return ball.getSize();
    }

    @Override
    public int getPaddleHeight(Player player) 
    {
        return player == Player.ONE ? (int) paddle1.getHeight() 
                :(int) paddle2.getHeight();
    }

    @Override
    public int getPaddleWidth(Player player) 
    {
        return player == Player.ONE ? (int) paddle1.getWidth()
                : (int) paddle2.getWidth();
    }

    @Override
    public Point getPaddleLocation(Player player) 
    {
        return player == Player.ONE ? paddle1.getLocation() :
                paddle2.getLocation();
    }
}

public class Pong {

    public static void main(String[] args) {
        PongModelInterface model = new MockPongModel();
        JFrame app = new PongFrame(model);   
        
    }
}
